using System.Text;
using AFEParserWPF.Models;

namespace AFEParserWPF.Services;

/// <summary>
/// 将 AFE 报告数据格式化为人类可读的文本
/// </summary>
public class ReportFormatter
{
    public static string FormatAFE2Report(AtmosphereFatalErrorCtx report)
    {
        var sb = new StringBuilder();
        sb.AppendLine("=== Fatal Report (AFE2) ===\n");
        
        AppendReportDesc(sb, report.Magic, report.ErrorDesc, report.TitleId);
        sb.AppendLine();
        
        AppendRegisters(sb, report.Gprs, report.Pc, report.ModuleBase);
        sb.AppendLine();
        
        AppendMisc(sb, report.Pstate, report.Afsr0, report.Afsr1, report.Esr, report.Far, report.ReportIdentifier);
        sb.AppendLine();
        
        if (report.StackTraceSize > 0)
        {
            AppendStackTrace(sb, report.StackTrace, report.StackTraceSize, report.ModuleBase);
            sb.AppendLine();
        }
        
        if (report.StackDumpSize > 0)
        {
            AppendStackDump(sb, report.StackDump, report.StackDumpSize);
            sb.AppendLine();
        }
        
        AppendTlsDump(sb, report.Tls);
        
        return sb.ToString();
    }

    public static string FormatAFE1Report(AtmosphereFatalErrorCtx1 report)
    {
        var sb = new StringBuilder();
        sb.AppendLine("=== Fatal Report (AFE1) ===\n");
        
        AppendReportDesc(sb, report.Magic, report.ErrorDesc, report.TitleId);
        sb.AppendLine();
        
        AppendRegisters(sb, report.Gprs, report.Pc, report.ModuleBase);
        sb.AppendLine();
        
        AppendMisc(sb, report.Pstate, report.Afsr0, report.Afsr1, report.Esr, report.Far, report.ReportIdentifier);
        sb.AppendLine();
        
        if (report.StackTraceSize > 0)
        {
            AppendStackTrace(sb, report.StackTrace, report.StackTraceSize, report.ModuleBase);
            sb.AppendLine();
        }
        
        if (report.StackDumpSize > 0)
        {
            AppendStackDump(sb, report.StackDump, report.StackDumpSize);
        }
        
        return sb.ToString();
    }

    public static string FormatAFE0Report(AtmosphereFatalErrorCtx0 report)
    {
        var sb = new StringBuilder();
        sb.AppendLine("=== Fatal Report (AFE0) ===\n");
        
        AppendReportDesc(sb, report.Magic, report.ErrorDesc, report.TitleId);
        sb.AppendLine();
        
        AppendRegisters(sb, report.Gprs, report.Pc, 0);
        sb.AppendLine();
        
        AppendMisc(sb, report.Pstate, report.Afsr0, report.Afsr1, report.Esr, report.Far, report.ReportIdentifier);
        
        return sb.ToString();
    }

    private static void AppendReportDesc(StringBuilder sb, uint magic, uint errorDesc, ulong titleId)
    {
        string magicStr = System.Text.Encoding.ASCII.GetString(BitConverter.GetBytes(magic));
        sb.AppendLine($"Magic: {magicStr} (0x{magic:X8})");
        sb.AppendLine($"Error Description: 0x{errorDesc:X8}");
        sb.AppendLine($"Title ID: 0x{titleId:X16}");
    }

    private static void AppendRegisters(StringBuilder sb, ulong[] gprs, ulong pc, ulong moduleBase)
    {
        sb.AppendLine("Registers:");
        for (int i = 0; i < 29; i++)
        {
            string addr = FormatAddress(gprs[i], moduleBase);
            sb.AppendLine($"  X[{i:D2}]: {addr}");
        }
        sb.AppendLine($"  FP: {FormatAddress(gprs[29], moduleBase)}");
        sb.AppendLine($"  LR: {FormatAddress(gprs[30], moduleBase)}");
        sb.AppendLine($"  SP: {FormatAddress(gprs[31], moduleBase)}");
        sb.AppendLine($"  PC: {FormatAddress(pc, moduleBase)}");
    }

    private static void AppendMisc(StringBuilder sb, uint pstate, uint afsr0, uint afsr1, uint esr, ulong far, ulong reportId)
    {
        sb.AppendLine("Miscellaneous:");
        sb.AppendLine($"  pstate: 0x{pstate:X8}");
        sb.AppendLine($"  afsr0: 0x{afsr0:X8}");
        sb.AppendLine($"  afsr1: 0x{afsr1:X8}");
        sb.AppendLine($"  esr: 0x{esr:X8}");
        sb.AppendLine($"  far: 0x{far:X16}");
        sb.AppendLine($"  Report ID: 0x{reportId:X16}");
    }

    private static void AppendStackTrace(StringBuilder sb, ulong[] stackTrace, ulong size, ulong moduleBase)
    {
        sb.AppendLine("Stack Trace:");
        int maxSize = Math.Min((int)size, stackTrace.Length);
        for (int i = 0; i < maxSize; i++)
        {
            string addr = FormatAddress(stackTrace[i], moduleBase);
            sb.AppendLine($"  [{i:D2}]: {addr}");
        }
    }

    private static void AppendStackDump(StringBuilder sb, byte[] stackDump, ulong size)
    {
        sb.AppendLine("Stack Dump:     00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F");
        sb.AppendLine("-----------------------------------------------------------");
        int maxSize = Math.Min((int)size, stackDump.Length);
        for (int i = 0; i < maxSize; i += 0x10)
        {
            int count = Math.Min(0x10, maxSize - i);
            var hex = string.Join(" ", stackDump.AsSpan(i, count).ToArray().Select(b => $"{b:X2}"));
            sb.AppendLine($"{i:X6}    {hex}");
        }
    }

    private static void AppendTlsDump(StringBuilder sb, byte[] tlsDump)
    {
        sb.AppendLine("TLS Dump:       00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F");
        sb.AppendLine("-----------------------------------------------------------");
        for (int i = 0; i < tlsDump.Length; i += 0x10)
        {
            int count = Math.Min(0x10, tlsDump.Length - i);
            var hex = string.Join(" ", tlsDump.AsSpan(i, count).ToArray().Select(b => $"{b:X2}"));
            sb.AppendLine($"{i:X6}    {hex}");
        }
    }

    private static string FormatAddress(ulong addr, ulong moduleBase)
    {
        if (moduleBase > 0 && moduleBase != ulong.MaxValue && addr >= moduleBase && addr - moduleBase < 0x1_000_000)
        {
            return $"0x{addr:X16} (MOD_BASE + 0x{addr - moduleBase:X16})";
        }
        return $"0x{addr:X16}";
    }
}
